/*
 * Reksio - Memory Map Editor
 * Copyright (C) 2023 CERN
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * In applying this licence, CERN does not waive the privileges and immunities
 * granted to it by virtue of its status as an Intergovernmental Organization or
 * submit itself to any jurisdiction.
 */

#include "attributesmodel.h"
#include "memorynode.h"
#include "attributecontainer.h"
#include "attributevalidator.h"
#include "attributecontainervalidator.h"
#include "validatornode.h"
#include "commands.h"
#include "utils.h"
#include <QDebug>
#include <QBrush>
#include <QItemSelection>
#include <QFont>
AttributesModel::AttributesModel(AttributeContainer* root_item, NodesModel* nodes_model, QUndoStack* undoStack, QObject *parent):
    QAbstractItemModel (parent), rootItem(root_item), nodes_model(nodes_model), undoStack(undoStack)
{
    connect(this, &AttributesModel::dataChanged, this, QOverload<const QModelIndex &, const QModelIndex &, const QVector<int> &>::of(&AttributesModel::notifyTreeDataChanged));
    connect(this, &AttributesModel::rowsRemoved, this, QOverload<const QModelIndex&, int, int>::of(&AttributesModel::notifyTreeDataChanged));
    connect(this, &AttributesModel::rowsInserted, this, QOverload<const QModelIndex&, int, int>::of(&AttributesModel::notifyTreeDataChanged));
}

int AttributesModel::rowCount(const QModelIndex & parent) const
{
    if(parent.column() > 0)
        return 0;
    if(!parent.isValid())
        return rootItem->getAttributesSize() + rootItem->getContainersSize();
    if(isContainer(parent))
    {
        const AttributeContainer * parentItem = getContainer(parent);
        return parentItem->getAttributesSize() + parentItem->getContainersSize();
    }
    return 0;
}

int AttributesModel::columnCount(const QModelIndex & /*parent*/) const
{
    return 2;
}

QVariant AttributesModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();
    if(role == InternalPointer)
    {
        return QVariant::fromValue(index.internalPointer());
    }
    AttributeContainer * container = getContainer(index);
    Attribute* attribute = getAttribute(index);
    // may not be valid

    if(role == Qt::DisplayRole)
    {
        if(index.column() == 0)
        {
            if(isContainer(index))
                return QString::fromStdString(container->getName());
            else
                return QString::fromStdString(attribute->getName());
        }
        else if (index.column() == 1)
        {
            if(isContainer(index))
                return QVariant();
            else
                return getAttributeValue(index, role); //QString::fromStdString(attribute->getValue());
        }
    }
    else if(role == Qt::EditRole)
    {
        return getAttributeValue(index, role);
    }
    else if(role == Qt::BackgroundRole)
    {
        // not valid brush
        QBrush red_brush(Qt::red);
        // deprecated brush
        QBrush orange_brush(Qt::darkYellow);
        if(index.column() == 0)
        {
            if(isAttribute(index))
            {
                if(!attribute->isValid())
                    return red_brush;
                if(attribute->isDeprecated())
                    return orange_brush;
            }
            else
            {
                if(!container->isValid())
                    return red_brush;
                if(container->isDeprecated())
                    return orange_brush;
            }
        }

    }
//    else if(role == Qt::ForegroundRole)
//    {

//    }
    else if(role == Qt::FontRole)
    {
        QFont font;
        if(index.column() == 0)
            font.setBold(true);
        if(isAttribute(index))
        {
            if(!attribute->getValidator()->isEditable())
                font.setItalic(true);
        }
        return font;
    }
    else if (role == Qt::ToolTipRole)
    {
        if(isAttribute(index))
        {
            QString msg;
            const QString& tooltip = QString::fromStdString(attribute->getValidator()->getToolTip());
            if(tooltip.size())
                msg += tooltip + "\n";

            if(attribute->isDeprecated())
            {
                msg += "This attribute is deprecated!\n";
                auto deprecated_msg = attribute->getValidator()->getDeprecatedMessage();
                if(!deprecated_msg.empty())
                    msg += QString::fromStdString(deprecated_msg) + "\n";
            }
            const auto& failed_validators = attribute->getFailedValidators();
            if(!failed_validators.empty())
                msg += "Problem(s) with attribute:\n";
            for(const auto& failed_validator: failed_validators)
            {
                msg += QString::fromStdString(failed_validator->getMessage()) + "\n";
            }
            return msg;
        }
        else
        {
            QString error_msg;

            if(container->isDeprecated())
            {
                error_msg += "This attribute container is deprecated!\n";
                auto deprecation_msg = container->getValidator()->getDeprecatedMessage();
                if(!deprecation_msg.empty())
                    error_msg += QString::fromStdString(deprecation_msg) + "\n";
            }
            const auto& failed_attributes = container->getFailedValidators();

            if(!failed_attributes.empty())
                error_msg += "Problem(s) with attributes:\n";
            for(const auto& failed_attribute: failed_attributes)
            {
                for(const auto& failed_validator: failed_attribute.second)
                {
                    error_msg += QString::fromStdString(failed_validator->getMessage()) + "\n";
                }
            }
            for(const auto& missing_attr: container->getMissingAttributes())
            {
                std::vector<std::string> full_name = missing_attr->getFullName();
                if(full_name.size() > 1)
                {
                    const QString& attr_location = QString::fromStdString(join(full_name, "/"));
                    error_msg += "Missing attribute: " + attr_location + "\n";
                }
                else
                    error_msg += "Missing attribute: " + QString::fromStdString(missing_attr->getName()) + "\n";

            }
            return error_msg;
        }
    }
    return QVariant();
}

QVariant AttributesModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if(role != Qt::DisplayRole)
        return QVariant();
    if (orientation == Qt::Horizontal)
    {
        switch (section)
        {
            case 0:
                return tr("Attribute");

            case 1:
                return tr("Value");

            default:
                return QVariant();
        }
    }
    return QVariant();
}

Qt::ItemFlags AttributesModel::flags(const QModelIndex &index) const
{
    if(!index.isValid())
        return Qt::ItemIsDropEnabled;
    const Qt::ItemFlags not_editable = Qt::ItemIsSelectable | (QAbstractItemModel::flags(index) & ~Qt::ItemIsEditable);
    if(index.column() == 0)
        return not_editable;
    if(index.column() == 1)
    {
        if(isContainer(index))
            return not_editable;
        if(isAttribute(index))
        {
            const Attribute* attribute = getAttribute(index);
            if(!attribute->getValidator()->isEditable())
                return not_editable;
            if(!attribute->isEditable())
                return not_editable;
        }
    }
    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsSelectable;
}

bool AttributesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && role == Qt::EditRole && index.column() == 1 && isAttribute(index))
    {
        const QString& str_val = value.toString();
        if(str_val != data(index, Qt::EditRole).toString())
        {
            undoStack->push(new ChangeAttributeCommand(index, nodes_model->getIndex(rootItem->getParentNode()), str_val)); // will emit dataChanged
            return true;
        }
    }
    return false;
}

QModelIndex AttributesModel::index(int row, int column, const QModelIndex &parent) const
{
    if(!hasIndex(row, column, parent))
        return QModelIndex();
    if(!parent.isValid())
    {
        return createIndex(row, column, rootItem);
    }
    else
    {

        AttributeContainer* parentContainer = getContainer(parent); // may be root
        if(parentContainer)
        {
            return createIndex(row, column, parentContainer);
        }
        else
        {
            return QModelIndex();
        }
    }
}
QModelIndex AttributesModel::parent(const QModelIndex &index) const
{
    if(!index.isValid())
        return QModelIndex();
    if(!rootItem->getParentNode())
        return QModelIndex();
    AttributeContainer * parent_container = getParentContainer(index);
    if(parent_container == rootItem)
        return QModelIndex();
    return createIndex(parent_container->containerIndex() + parent_container->getParent()->getAttributesSize(), 0, parent_container->getParent());
}

QVariant AttributesModel::getAttributeValue(const QModelIndex &index, int role) const
{
	static_cast<void>(role); // suppress -Wunused-parameter

    if(isContainer(index))
        return QVariant();
    Attribute* attr = getAttribute(index);
    const std::string& type = attr->getValidator()->getAttributeType();
    QString value = QString::fromStdString(attr->getValue());
    if(type == "str")
        return value;
    else if (type == "bool")
    {
        value = value.toLower().trimmed();
        if(value == "true")
            return true;
        return false;
    }
    else if(type == "int")
    {
        bool ok;
        const long long int ll_number = value.toLongLong(&ok, 0); // can be hex or dec
        if(ok)
            return ll_number;
    }
    else if(type == "hex")
    {
        bool ok;
        const long long int number = value.toLongLong(&ok, 0); // can be hex or dec
        if(ok)
        {
            std::ostringstream result;
            result << "0x" << std::uppercase << std::hex << static_cast<uintptr_t>(number);
            return QString::fromStdString(result.str());
        }
    }
    else if(type == "float")
    {
        bool ok;
        double number = value.toDouble(&ok);
        if(ok)
            return number;
    }
    return value;
}

const NodeRangeValidator *AttributesModel::getRangeValidator(const QModelIndex &index) const
{
    NodeRangeValidator * validator = nullptr;
    return getValidator(validator, index);
}

const NodeRegexValidator *AttributesModel::getRegexValidator(const QModelIndex &index) const
{
    NodeRegexValidator* validator = nullptr;
    return getValidator(validator, index);
}

const NodeEnumValidator *AttributesModel::getEnumValidator(const QModelIndex &index) const
{
    NodeEnumValidator* validator = nullptr;
    return getValidator(validator, index);
}

const PyNodeEnumValidator *AttributesModel::getPyEnumValidator(const QModelIndex &index) const
{
    PyNodeEnumValidator* validator = nullptr;
    return getValidator(validator, index);
}

bool AttributesModel::addAttribute(const QModelIndex& parent, const QString name, const QString val)
{
    return addAttribute(parent, std::make_unique<Attribute>(name.toStdString(), val.toStdString()));
}

bool AttributesModel::addAttribute(const QModelIndex& parent, std::unique_ptr<Attribute> attribute)
{
    if(!isContainer(parent))
        return false;
    AttributeContainer* container = getContainer(parent);
    if(container->hasAttribute(attribute->getName()))
        return false;
    const int attributes_size = container->getAttributesSize();
    beginInsertRows(parent.siblingAtColumn(0), attributes_size, attributes_size); // add at the end
    container->addAttribute(std::move(attribute));
    endInsertRows();
    return true;
}

std::unique_ptr<Attribute> AttributesModel::removeAttribute(const QModelIndex& parent, const QString name)
{
    if(isContainer(parent))
    {
        AttributeContainer* container = getContainer(parent);
        return removeAttribute(parent, container->attributeIndex(name.toStdString()));
    }
    return nullptr;
}

std::unique_ptr<Attribute> AttributesModel::removeAttribute(const QModelIndex& parent, int position)
{
    if(!isContainer(parent))
        return nullptr;
    if (position < 0 || position >= rowCount(parent))
        return nullptr;
    AttributeContainer* container = getContainer(parent);
    beginRemoveRows(parent.siblingAtColumn(0), position, position);
    std::unique_ptr<Attribute> attribute = container->releaseAttribute(position);
    container->removeAttribute(position);
    endRemoveRows();
    return attribute;
}

bool AttributesModel::addAttributeContainer(const QModelIndex &parent, std::unique_ptr<AttributeContainer> attribute_container)
{
    return addAttributeContainer(rowCount(parent), parent, std::move(attribute_container)); // add at the end
}

bool AttributesModel::addAttributeContainer(int position, const QModelIndex &parent, std::unique_ptr<AttributeContainer> attribute_container)
{
    if(!isContainer(parent))
        return false;
    AttributeContainer* parent_container = getContainer(parent);
    if(position < parent_container->getAttributesSize())
        return false;
    if(parent_container->hasContainer(attribute_container->getName()))
        return false;
    beginInsertRows(parent.siblingAtColumn(0), position, position);
    parent_container->addContainer(std::move(attribute_container));
    endInsertRows();
    return true;
}

bool AttributesModel::addAttributeContainer(int position, const QModelIndex &parent, const QString name)
{
    return addAttributeContainer(position, parent, std::make_unique<AttributeContainer>(name.toStdString()));
}

bool AttributesModel::addAttributeContainer(const QModelIndex &parent, const QString name)
{
    return addAttributeContainer(rowCount(parent), parent, name);
}

bool AttributesModel::addAttributeContainers(const QModelIndex &parent, const std::vector<std::string> names)
{
    if(!isContainer(parent))
        return false;
    const int position = rowCount(parent);
    beginInsertRows(parent.siblingAtColumn(0), position, position);
    AttributeContainer * parent_container = getContainer(parent);
    parent_container->addContainers(names);
    endInsertRows();
    return true;
}

std::unique_ptr<AttributeContainer> AttributesModel::removeAttributeContainer(const QModelIndex &parent, const QString name)
{
    if(isContainer(parent))
    {
        AttributeContainer* container = getContainer(parent);
        return removeAttributeContainer(parent, container->containerIndex(name.toStdString()));
    }
    return nullptr;
}

std::unique_ptr<AttributeContainer> AttributesModel::removeAttributeContainer(const QModelIndex &parent, int position)
{
    if(!isContainer(parent))
        return nullptr;
    AttributeContainer* parent_container = getContainer(parent);
    if(position < 0 || position >= parent_container->getContainersSize())
        return nullptr;
    const int model_position = position + parent_container->getAttributesSize();
    beginRemoveRows(parent.siblingAtColumn(0), model_position, model_position);
    std::unique_ptr<AttributeContainer> container = parent_container->releaseAttributeContainer(position);
    parent_container->removeAttributeContainer(position);
    endRemoveRows();
    return container;
}

AttributeContainer *AttributesModel::getContainer(const QModelIndex &index) const
{
    if(!index.isValid())
        return rootItem;
    AttributeContainer * parent_container = getParentContainer(index);
    const int& row = index.row();
    const int& attributes_size = parent_container->getAttributesSize();
    const int container_index = row - attributes_size;
    if(row >= attributes_size)
    {
        // its a container
        return parent_container->getContainers().at(static_cast<size_t>(container_index)).get();
    }
    return nullptr;
}

AttributeContainer *AttributesModel::getParentContainer(const QModelIndex &index) const
{
    if(index.isValid())
    {
        return static_cast<AttributeContainer*>(index.internalPointer());
    }
    else
        return rootItem;
}

Attribute *AttributesModel::getAttribute(const QModelIndex &index) const
{
    if(index.isValid())
    {
        AttributeContainer * parent_container = getParentContainer(index);
        const int& row = index.row();
        const int& attributes_size = parent_container->getAttributesSize();
        if(row < attributes_size)
        {
            return parent_container->getAttribute(row);
        }
    }
    return nullptr;
}

NodesModel *AttributesModel::getNodesModel() const
{
    return nodes_model;
}

AttributesModel *AttributesModel::getAttributesModel(const QModelIndex &index)
{
    return const_cast<AttributesModel*>(static_cast<const AttributesModel*>(index.model()));
}

QModelIndex AttributesModel::getIndex(Attribute *attribute) const
{
    AttributeContainer* container = attribute->getParent();
    return createIndex(container->attributeIndex(attribute->getName()), 0, container);
}

QModelIndex AttributesModel::getIndex(AttributeContainer *attribute_container) const
{
    if(attribute_container->isRoot())
        return QModelIndex();
    AttributeContainer* parent_container = attribute_container->getParent();
    QModelIndexList list = match(index(0, 0), InternalPointer, QVariant::fromValue(static_cast<void*>(parent_container)), -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
    for(const auto& index: list)
    {
        if(isContainer(index))
        {
            if(getContainer(index) == attribute_container)
                return index;
        }
    }
    return QModelIndex();
}

MemoryNode *AttributesModel::getParentMemoryNode()
{
    return rootItem->getParentNode();
}

bool AttributesModel::isAttribute(const QModelIndex &index) const
{
    const AttributeContainer* parent_container = getParentContainer(index);
    return index.row() < parent_container->getAttributesSize();
}

bool AttributesModel::isContainer(const QModelIndex &index) const
{
    const AttributeContainer* parent_container = getParentContainer(index);
    if(index.isValid())
        return index.row() >= parent_container->getAttributesSize();
    return true;
}

bool AttributesModel::isEditable(const QModelIndex &index) const
{
    return flags(index).testFlag(Qt::ItemIsEditable);
}

void AttributesModel::notifyTreeDataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &)
{
    notifyTreeDataChanged();
}

void AttributesModel::notifyTreeDataChanged(const QModelIndex &, int, int)
{
    notifyTreeDataChanged();
}

void AttributesModel::notifyTreeDataChanged()
{
    const QModelIndex& node_index = nodes_model->getIndex(getParentMemoryNode());
    Q_EMIT(nodes_model->dataChanged(node_index, node_index.siblingAtColumn(1))); // notify both columns
}


